
package net.qiqbframework.messaging.annotation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.ref.WeakReference;
import java.util.*;

import static java.util.ServiceLoader.load;

public final class ClasspathHandlerEnhancerDefinition {

    private static final Logger LOGGER = LoggerFactory.getLogger(ClasspathHandlerEnhancerDefinition.class);
    private static final Object MONITOR = new Object();
    private static final Map<ClassLoader, WeakReference<MultiExecutableHandlerEnhancer>> FACTORIES = new WeakHashMap<>();

    private ClasspathHandlerEnhancerDefinition() {
        // not meant to be publicly instantiated
    }

    /**
     * Creates an instance for the given {@code clazz}. Effectively, the class loader of the given class is used to
     * locate implementations.
     *
     * @param clazz The class for which the handler definition must be returned
     * @return a MultiHandlerEnhancerDefinition that can create handlers for the given class
     */
    public static MultiExecutableHandlerEnhancer forClass(Class<?> clazz) {
        return forClassLoader(clazz == null ? null : clazz.getClassLoader());
    }

    /**
     * Creates an instance using the given {@code classLoader}. Implementations are located using this class loader.
     *
     * @param classLoader The class loader to locate the implementations with
     * @return a MultiHandlerEnhancerDefinition instance using the given classLoader
     */
    public static MultiExecutableHandlerEnhancer forClassLoader(ClassLoader classLoader) {
        synchronized (MONITOR) {
            MultiExecutableHandlerEnhancer enhancerDefinition;
            if (!FACTORIES.containsKey(classLoader)) {
                enhancerDefinition = MultiExecutableHandlerEnhancer.ordered(findDelegates(classLoader));
                FACTORIES.put(classLoader, new WeakReference<>(enhancerDefinition));
                return enhancerDefinition;
            }
            enhancerDefinition = FACTORIES.get(classLoader).get();
            if (enhancerDefinition == null) {
                enhancerDefinition = MultiExecutableHandlerEnhancer.ordered(findDelegates(classLoader));
                FACTORIES.put(classLoader, new WeakReference<>(enhancerDefinition));
            }
            return enhancerDefinition;
        }
    }

    private static MultiExecutableHandlerEnhancer findDelegates(ClassLoader classLoader) {
        Iterator<ExecutableHandlerWrapper> iterator = load(ExecutableHandlerWrapper.class, classLoader == null ?
                Thread.currentThread().getContextClassLoader() : classLoader).iterator();
        //noinspection WhileLoopReplaceableByForEach
        final List<ExecutableHandlerWrapper> enhancers = new ArrayList<>();
        while (iterator.hasNext()) {
            try {
                ExecutableHandlerWrapper factory = iterator.next();
                enhancers.add(factory);
            } catch (ServiceConfigurationError e) {
                LOGGER.info("HandlerEnhancerDefinition instance ignored, as one of the required classes is not availableon the classpath: {}", e.getMessage());
            } catch (NoClassDefFoundError e) {
                LOGGER.info("HandlerEnhancerDefinition instance ignored. It relies on a class that cannot be found: {}",
                            e.getMessage());
            }
        }
        return new MultiExecutableHandlerEnhancer(enhancers);
    }

}
